﻿//#define EXPAND_MAPPING_TOKEN

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Text;

using agree.configuration;
using agree.rewriter;
using agree.schema;

using alib;
using alib.Collections;
using alib.Debugging;
using alib.Enumerable;
using alib.Reflection;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class TokenMappingGeometry : identity_node, ρρIsometrics
	{
		public TokenMappingGeometry(IRtParent parent, IIdentity prv)
			: base(parent, prv)
		{
			EntryMgr em;
			var _tmp = parent as Isometrics ?? prv as Isometrics;
			if (_tmp == null && (em = parent as EntryMgr) != null)
				_tmp = em.im;
			this.Isometrics = _tmp;
		}
		public TokenMappingGeometry(IRtParent parent)
			: this(parent, parent)
		{
		}


		///////////////////////////////////////////////////////////////////////
		///
		Isometrics im;
		public Isometrics Isometrics
		{
			get { return this.im; }
			set
			{
				if ((this.im = value) != null)
					init(im);
			}
		}
		///
		///////////////////////////////////////////////////////////////////////


		[DebuggerDisplay("{im==null||im.ftm==null?ifeat_plus_input.ToString():im.ftm[ifeat_plus_input].ToString(),nq}")]
		public int ifeat_plus_input;
		[DebuggerDisplay("{im==null||im.ftm==null?ifeat_plus_context.ToString():im.ftm[ifeat_plus_context].ToString(),nq}")]
		public int ifeat_plus_context;
		[DebuggerDisplay("{im==null||im.ftm==null?ifeat_plus_output.ToString():im.ftm[ifeat_plus_output].ToString(),nq}")]
		public int ifeat_plus_output;

		public FsPath LexiconTokensPath;
		public FsPath LexiconLastTokenPath;
		public FsPath TokenFormPath;
		public FsPath TokenCargPath;
		public FsPath TokenIdPath;
		public FsPath TokenFromPath;
		public FsPath TokenToPath;
		public FsPath TokenPostagsPath;
		public FsPath TokenPosprobsPath;

		public FsPath ChartMappingContextPath;
		public FsPath ChartMappingInputPath;
		public FsPath ChartMappingOutputPath;
		public FsPath ChartMappingPositionPath;

		public void init(Isometrics im)
		{
			TokenMappingConfig cfg = im.AgreeConfig.TokenMapping;

			LexiconTokensPath /*			*/ = new FsPath(im, cfg.LexiconTokensPath);
			LexiconLastTokenPath /*			*/ = new FsPath(im, cfg.LexiconLastTokenPath);
			TokenFormPath /*				*/ = new FsPath(im, cfg.TokenFormPath);
			TokenCargPath /*				*/ = new FsPath(im, cfg.TokenCargPath);
			TokenIdPath /*					*/ = new FsPath(im, cfg.TokenIdPath);
			TokenFromPath /*				*/ = new FsPath(im, cfg.TokenFromPath);
			TokenToPath /*					*/ = new FsPath(im, cfg.TokenToPath);
			TokenPostagsPath /*				*/ = new FsPath(im, cfg.TokenPostagsPath);
			TokenPosprobsPath /*			*/ = new FsPath(im, cfg.TokenPosprobsPath);
			ChartMappingContextPath /*		*/ = new FsPath(im, cfg.ChartMappingContextPath);
			ChartMappingInputPath /*		*/ = new FsPath(im, cfg.ChartMappingInputPath);
			ChartMappingOutputPath /*		*/ = new FsPath(im, cfg.ChartMappingOutputPath);
			ChartMappingPositionPath /*		*/ = new FsPath(im, cfg.ChartMappingPositionPath);

			ifeat_plus_context /*			*/ = ChartMappingContextPath.FirstIx;
			ifeat_plus_input /*				*/ = ChartMappingInputPath.FirstIx;
			ifeat_plus_output /*			*/ = ChartMappingOutputPath.FirstIx;
		}

		public Element.Mode GetContextSensitiveMode(int i_feat)
		{
			if (i_feat == ifeat_plus_context)
				return Element.Mode.CONTEXT;
			if (i_feat == ifeat_plus_input)
				return Element.Mode.INPUT;
			if (i_feat == ifeat_plus_output)
				return Element.Mode.OUTPUT;
			return Element.Mode.ERROR;
		}

		public ModeIx CSRModeIx(FsPath fsp)
		{
			if (fsp.Count < 3)	// need at minimum: [CONTEXT FIRST (data node)]
				return ModeIx.Error;

			Element.Mode mode = GetContextSensitiveMode(fsp[0]);
			if (mode == Element.Mode.ERROR)
				return ModeIx.Error;

			return new ModeIx(mode, fsp.GetListIndex(1));
		}

		public FsPath GetReadbackPath(ILexeme le)
		{
			return le is GenericLexicalEntry ? TokenFormPath : TokenCargPath;
		}

		public String ReadbackTokensForm(ITfsSlot ts, ILexeme le)
		{
			FsPath fs2;
			if (!LexiconTokensPath.IsEmpty && !(fs2 = GetReadbackPath(le)).IsEmpty)
			{
				var gw = LexiconTokensPath[ts].ListSlots.Where(x => x.IsValid).ToArray();
				if (gw.Length == 1)
					return gw[0][fs2].StringValue;
				else if (gw.Length > 1)
				{
					String[] rgs = new String[gw.Length];
					for (int j = 0; j < rgs.Length; j++)
						if ((rgs[j] = gw[j][fs2].StringValue) == null)
							return null;
					return (le.Tokenizer ?? SplitTokenizer.SpaceCharTokenizer).MakeSurface(rgs);
				}
			}
			return null;
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Build mapping tokens from a stream of text tokens. For the time being we prepare the token as TDL text, and then use 
	/// the TDL text parser for assistance. In the future, a lighter-weight machine-oriented (as opposed to human-readable)
	/// intermediary (e.g. 'tdl-DOM'?) might be nice.
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class MappingTokenBuilder : m_scalar_base<IIdentList<IString>, IdentArray<MappingToken>>,
		ρρEntryMgr
	{
		public MappingTokenBuilder(IRtParent parent, IIdentList<IString> sent)
			: base(parent, sent)
		{
		}


		///////////////////////////////////////////////////////////////////////
		///
		TypeDictionary td;
		TokenMappingGeometry tmg;
		EntryMgr em;
		public EntryMgr EntryMgr
		{
			get { return em; }
			set { em = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		protected override IdentArray<MappingToken> start(IIdentList<IString> sent)
		{
			td = em.td;
			tmg = em.TokenMappingGeometry;

			Tdl[] rgtdl = new Tdl[sent.Count];

			var tdl_all = get_toks_tdl(rgtdl);

			var tgrp = new TdlConstraintGrouper(this, new TdlTokenizer(this, tdl_all));

			var tokg = Observable.Wait(tgrp).constraints;

			var ttdl = TfsBuilder.Tdl.Build(this, em.ftm.r_none, tokg);

			var tokfs = new ArrayTfs(this, ttdl, Tfs.Flags.None);

#if EXPAND_MAPPING_TOKEN
			tokfs = (ArrayTfs)Unification.Expand(ftm.r_none, tokfs, Tfs.Flags.RootCoverage);
#else
			/* well-formedness expansion is enforced during the first unification */
#endif
			var ls = tokfs[tmg.LexiconTokensPath].ListSlots;

			Debug.Assert(ls.Length == sent.Count);

			return setup_tokens_arr(rgtdl, ls);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		Tdl get_toks_tdl(Tdl[] rgtdl)
		{
			var sb = new StringBuilder();
			String s;

			for (int i = 0; i < rgtdl.Length; i++)
			{
				rgtdl[i] = new Tdl(t0[i], s = get_token_tdl(t0[i], i + 1));

				if (i > 0)
					sb.AppendLine(",");
				sb.Append(s);
			}

			var s_tdl_all = String.Format("_tl := {0} & [ {1} <\r\n{2} > ].",
				td.ΔTop.Name,
				tmg.LexiconTokensPath.TdlString,
				sb.ToString());

			return new Tdl(this, s_tdl_all);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		IdentArray<MappingToken> setup_tokens_arr(Tdl[] s_tdl_each, TfsSlot[] ls)
		{
			var arr = new MappingToken[ls.Length];
			for (int i = 0; i < arr.Length; i++)
				(arr[i] = new MappingToken(this, ls[i], i)).SourceTdl = s_tdl_each[i];

			return new IdentArray<MappingToken>(t0, arr);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		String get_token_tdl(IString istr_word, int tok_id)
		{
			if (tmg.TokenFormPath.TdlString == String.Empty)
				throw new Exception("feature path for token FORM not found in configuration");

			String s_word = istr_word.Text;
			td.strings.Add(s_word);

			String s_tdl = String.Format("{0} \"{1}\"",
				tmg.TokenFormPath,
				s_word);

			if (tmg.TokenIdPath.TdlString == String.Empty)
				throw new Exception("feature path for token ID not found in configuration");

			s_tdl += String.Format(", {0} <! ({1}) !> ", tmg.TokenIdPath.TdlString, tok_id);

			var csw = istr_word as SourceWord;
			if (csw != null)
			{
				if (tmg.TokenFromPath.TdlString == String.Empty || tmg.TokenToPath.TdlString == String.Empty)
					throw new Exception("feature path for FROM or TO not found in configuration");

				s_tdl += String.Format(", {0} ({1})",
					tmg.TokenFromPath.TdlString,
					csw.SourceCharacterStart);

				s_tdl += String.Format(", {0} ({1})",
					tmg.TokenToPath.TdlString,
					csw.SourceCharacterEnd);

				if (csw.HasPosTags)
				{
					if (tmg.TokenPostagsPath.TdlString == String.Empty || tmg.TokenPosprobsPath.TdlString == String.Empty)
						throw new Exception("feature path for token POS tags or POS probabilities not found in configuration");

					s_tdl += String.Format(", {0} < {1} >",
						tmg.TokenPostagsPath.TdlString,
						csw.PosProbs.Select(pp => "\"" + pp.pos + "\"").StringJoin(", "));

					s_tdl += String.Format(", {0} < {1} >",
						tmg.TokenPosprobsPath.TdlString,
						csw.PosProbs.Select(pp => String.Format("\"{0:G7}\"", pp.p)).StringJoin(", "));
				}
			}

			return " [ " + s_tdl + " ]";
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// basically, a TfsSlot (a pointer into the interior of some actual Tfs--which is itself irrelevant--but which will
	/// in fact be an _atfs_base instance) which adheres to the format for a DELPH-IN style 'token'. Although typically
	/// a given Tfs may host several MappingTokens, the self-hosting (per MappingToken) scenario should, in theory,
	/// work as well.
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq}")]
	public class MappingToken : IdentitySlotRef,
		IWordToken,
		ρρSourceTdl,
		ρρPosProbs
	{
		public MappingToken(IIdentity prv, ITfsSlot ts)
			: base(prv, ts)
		{
			this.tmg = tfs.em.TokenMappingGeometry;
		}
		public MappingToken(IIdentity prv, ITfsSlot ts, int ix)
			: this(prv, ts)
		{
			this.tok_span = new ChartSpan(ix, ix);
		}
		public MappingToken(IIdentity prv, ITfsSlot ts, Tdl source_tdl)
			: this(prv, ts)
		{
			this._tdl = source_tdl;
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		readonly TokenMappingGeometry tmg;

		/// <summary>
		/// This is not the mapping token identifier (and it cannot be since that 'id' can be list-valued)
		/// </summary>
		long IIdentityId.Id { get { return tfs.Id; } }

		///////////////////////////////////////////////////////////////////////
		/// 
		Tdl _tdl;
		public Tdl SourceTdl
		{
			get { return _tdl; }
			set { _tdl = value; }
		}
		/// 
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		/// FIX FIX
		ChartSpan tok_span;
		public ChartSpan TokenSpan
		{
			get { return this.tok_span; }
			set { this.tok_span = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public String SurfaceForm
		{
			get { return this[tmg.TokenFormPath].StringValue; }
			//get { return this[tmg.TokenCargPath].StringValue; }
			set { throw not.valid; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public String Text
		{
			//get { return this[tmg.TokenCargPath].StringValue; }
			get { return this[tmg.TokenFormPath].StringValue; }
			set { throw not.valid; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		public int SourceCharacterStart
		{
			get { return this[tmg.TokenFromPath].IntegerValue; }
			set { throw not.valid; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		public int SourceCharacterEnd
		{
			get { return this[tmg.TokenToPath].IntegerValue; }
			set { throw not.valid; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		public int[] TokenIdList
		{
			get
			{
				var rgt = this[tmg.TokenIdPath];
				if (!rgt.IsValid)
					return Collection<int>.None;
				return rgt.DiffListListSlots.Select(ls => ls.IntegerValue).ToArray();
			}
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		public alib.Hmm.PosProb[] PosProbs
		{
			get
			{
				int i, c;
				TfsSlot[] ef1, ef2;

				if ((c = (ef1 = this[tmg.TokenPostagsPath].ListSlots).Length) == 0)
					return Collection<alib.Hmm.PosProb>.None;
				if ((i = (ef2 = this[tmg.TokenPosprobsPath].ListSlots).Length) < c)
					c = i;
				if (c == 0)
					return Collection<alib.Hmm.PosProb>.None;

				alib.Hmm.PosProb pp;
				alib.Hmm.PosProb[] arr = new alib.Hmm.PosProb[c];

				for (i = 0; i < c; i++)
				{
					if ((pp.pos = ef1[i].StringValue) == null || Double.IsNaN(pp.p = ef2[i].DoubleValue))
					{
						alib.Array.arr.Resize(ref arr, i);
						break;
					}
					arr[i] = pp;
				}
				return arr;
			}
			set { throw not.valid; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		public String ToYY(int edge_id, int lattice_path)
		{
			return String.Format("({0}, {1}, {2}, {3}, {4}, \"{5}\", 0)",
				edge_id,
				TokenSpan.StartIndex,
				TokenSpan.EndIndex + 1,
				CharSpanDisplay,
				lattice_path,
				SurfaceForm);
		}

		public String CharSpanDisplay { get { return String.Format("<{0}:{1}>", SourceCharacterStart, SourceCharacterEnd); } }

		public String TokenSpanDisplay { get { return String.Format("{0}{1}", TokenSpan.StartIndex, TokenSpan.EndIndex + 1); } }

		public String IdDisplay { get { return String.Format("[{0}]", TokenIdList.StringJoin(",")); } }

		public override String ToString()
		{
#if true
			return String.Format("{0} {1} {2}", IdDisplay, TokenSpanDisplay, SurfaceForm);
#else
			String s = base.ToString();
			if (s.EndsWith("." + this.GetType().Name))
				s = "";

			var tl = this.TokenIdList;
			if (tl.Length == 0)
				s += " (no tokens)";
			else
				s += " " + ToYY(TokenIdList[0], 1);	/* not sure how to render multiple ids in YY format */

			//return agree.Tfs.MonospaceFormatter.Format(this, false) + Environment.NewLine;
			return s;
#endif
		}

		[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
		TfsSlot _dbg_ts { get { return base.TfsSlot; } }
	};
}
